package ChessEngine;

import iodevice.Kdoparser;

import java.util.HashMap;
import java.util.LinkedList;

import ChessEngine.moveGenerator.MoveGenerator;
import ChessEngine.moveGenerator.Exceptions.BoardException;
import boardData.Board;

public class AlphaBeta extends AbstractSearch {

	private HashMap<Long, TranspositionTableEntry> transpositionTable;
	private String bestMove;
	private int actualSearchDepth;
	private GameTree tree;
	private SearchThread searcher;

	public AlphaBeta() {
		actualSearchDepth = 0;
		tree = new GameTree();
		transpositionTable = new HashMap<Long, TranspositionTableEntry>();
	}

	@Override
	public boolean stopSearching() {
		searcher.interrupt();
		Kdoparser.giveOrder("move"+bestMove);
		return searcher.isInterrupted();
	}

	@Override
	public String calculateNextMove(Board b, boolean white, int secsToCalc)
			throws BoardException {

		searcher = new SearchThread(b, white, secsToCalc);
		searcher.start();

		return bestMove;
	}
	
	public String getBestMove() {
		return bestMove;
	}

	/**
	 * Jeder Suchalgorithmus führt die Suche in einem eigenem Thread durch. Dies
	 * hat den Vorteil, dass falls ein Stop Kommando kommt einfach der thread
	 * unterbrochen werden muss. Der Suchalgorithmus bekommt alle notwendigen
	 * informationen (wie z.B. das Board auf dem gesucht werden soll und die
	 * zeit) bei der Erzeugung eines des Objekts übergeben. Um die suche zu
	 * starten muss dann nur noch die start methode aufgerufen werden. Im Laufe
	 * der Suche wird der bisher beste gefundene Zug in der Variablen bestMove
	 * gespeichert. Diese existiert auch noch nachdem der Thread (die Suche)
	 * fertig ist oder oder unterbrochen wurde und kann daher einfach von der
	 * MainEngine abgefragt werden.
	 */
	protected class SearchThread extends Thread {

		// private GameTree tree;
		private MoveGenerator moveGenerator;
		private AbstractEvalutor evaluator;
		private int secsToCalc;

		public SearchThread(Board b, boolean white, int secsToCalc) {
			this.moveGenerator = new MoveGenerator();
			// evaluator = new PieceSquareTableEvaluator();
			this.evaluator = new SimpleEvaluator();
			this.secsToCalc = secsToCalc;
			
			/*
			 * im moment wird für jeden neue Suche ein neuer Spielbaum
			 * aufgebaut. es wäre besser, wenn man jeweils an die ergebnisse der
			 * vorherigen suche anknüfen würde also den Suchbaum nur einmal bei
			 * erzeugug des objekts machen und nur den den neuen teilbaum weiter
			 * durchsuchen...
			 */
			tree = new GameTree();
			tree.setRootNode(b, b.isSideWhite());
		}

		@Override
		public void run() {

			long startTime = System.currentTimeMillis();
			
			actualSearchDepth = 0;
			int depth = 2;
			while (startTime < (System.currentTimeMillis() + secsToCalc * 1000)) {
				if(isInterrupted()){
					System.out.println("Thread wurde unterbrochen...");
					Kdoparser.giveOrder("move"+bestMove);
					return;
				}
				try {
					bestMove = alphaBetaRoot(tree.getRoot(), depth);
					Kdoparser.giveOrder("move"+bestMove);
					System.out.println("Beste Bewertung bei Tiefe" + depth
							+ ": " + bestMove);
				} catch (BoardException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

		}

		public int alphaBeta(Node node, int alpha, int beta, int depth)
				throws BoardException {
			if(isInterrupted()){
				Kdoparser.giveOrder("move"+bestMove);
				return -1;
			}
			actualSearchDepth++;
			if (actualSearchDepth == depth) {
				// Blattknoten an Suchtiefe erreicht, Knoten bewerten und
				// Suchtiefe
				// verringern.
				actualSearchDepth--;
				if (node.getEvaluation() == 0) {
					node.setEvaluation(evaluator.evaluate(node.getBoardData()));
				}
				return node.getEvaluation();
			} else {
				if (!(node.hasChildren())) {
					// Suchtiefe noch nicht erreicht und knoten hat keine
					// Kinder,
					// also anhaengen.
					LinkedList<Board> possibleMoves = moveGenerator
							.generateMove(node.getBoardData(), node
									.getBoardData().isSideWhite());
					node.setList(possibleMoves);
				}
			}

			// alle kindknoten durchlaufen
			for (Node childNode : node.getList()) {
				/*
				 * falls ein interrupt gesetzt wurde aufhören...
				 */
				if(isInterrupted()){
					Kdoparser.giveOrder("move"+bestMove);
					return -1;
				}
				/*
				 * stack anlegen und schauen ob aktueller Knoten ein
				 * "guter"knoten ist.(in transposition table) falls nein diesesn
				 * auf dem stack speichern und weiter falls ja knoten
				 */
				final TranspositionTableEntry tte = transpositionTable
						.get(childNode.getBoardData().hashCode());

				if (node.isWhiteMove()) {
					// weiss ist am zug, Bewertung des Aktuellen Knoten
					// ermitteln
					// ist bewertung vlt schon in transposition table vorhanden?

					int subResult = 0;
					if (tte == null) {
						// Knoten wurde noch nicht durchsucht, normal
						// weitermachen

						subResult = alphaBeta(childNode, alpha, beta, depth);
					} else {
						String historyResultType = tte.getType();

						if (historyResultType.equals("pv")) {

						} else if (historyResultType.equals("cut")) {

						} else {

						}
					}
					alpha = Math.max(alpha, subResult);
					node.setEvaluation(alpha);
					if (alpha >= beta) {
						// hier wird der Baum Abgeschnitten, die knoten aus der
						// liste loeschen, alpha cut
						int firstNodeToRemove = node.getList().indexOf(
								childNode);
						for (int j = node.getList().size() - 1; j > firstNodeToRemove; j--) {
							node.getList().removeLast();
						}
						childNode.cutOff = true;
						actualSearchDepth--;
						return alpha;
					}
				} else {
					// in transposition table nachschauen ob vlt schon bewertet.

					int subResult = alphaBeta(childNode, alpha, beta, depth);

					beta = Math.min(beta, subResult);
					node.setEvaluation(beta);

					if (alpha >= beta) {
						// hier wird der Baum Abgeschnitten, die knoten aus der
						// liste loeschen, beta cut
						int firstNodeToRemove = node.getList().indexOf(
								childNode);
						for (int j = node.getList().size() - 1; j > firstNodeToRemove; j--) {
							node.getList().removeLast();
						}
						childNode.cutOff = true;
						actualSearchDepth--;
						return beta;
					}
				}
			}
			actualSearchDepth--;

			if (node.isWhiteMove()) {
				return alpha;
			} else {
				return beta;
			}

		}

		public String alphaBetaRoot(Node node, int depth) throws BoardException {
			if (node.getList() == null || node.getList().isEmpty()) {
				LinkedList<Board> possibleMoves = moveGenerator.generateMove(
						node.getBoardData(), node.getBoardData().isSideWhite());

				node.setList(possibleMoves);
			}
			for (Node bo : node.getList()) {
				System.out.println(bo.getBoardData().toString());
			}

			int alpha = Integer.MIN_VALUE;
			int beta = Integer.MAX_VALUE;
			Board bestBoard = null;
			for (Node n : node.getList()) {
				if(isInterrupted()){
					Kdoparser.giveOrder("move"+bestMove);
					System.out.println("Thread wurde unterbrochen...");
					return"";
				}
				int val = alphaBeta(n, alpha, beta, depth);
				n.setEvaluation(val);
				if (node.isWhiteMove()) {
					if (val > alpha) {
						alpha = val;
						node.setEvaluation(val);
						bestBoard = n.getBoardData();
					}
				} else {
					if (val < beta) {
						beta = val;
						node.setEvaluation(val);
						bestBoard = n.getBoardData();
					}
				}

			}
			return bestBoard.getCurrMove();
		}		
	}

	public static GameTree buildTestTree() {
		String[] fen = new String[2];
		fen[0] = "7k/8/8/5Q2/8/8/8/6K1 w - - 0 1";
		fen[1] = "";
		final Board rootBoard = new Board(fen);
		GameTree t = new GameTree();
		t.setRootNode(rootBoard, true);

		Node root = t.getRoot();
		LinkedList<Board> rootList = new LinkedList<Board>();
		rootList.add(rootBoard);
		rootList.add(rootBoard);
		root.setList(rootList);
		Node rootChildNode1 = root.getList().get(0);// s2
		Node rootChildNode2 = root.getList().get(1);// s3

		LinkedList<Board> rootChild1List = new LinkedList<Board>();
		rootChild1List.add(rootBoard);
		rootChild1List.add(rootBoard);
		rootChildNode1.setList(rootChild1List);

		Node level2Child1 = rootChildNode1.getList().get(0);
		level2Child1.setEvaluation(4);// s4
		Node level2Child2 = rootChildNode1.getList().get(1);
		level2Child2.setEvaluation(3);// s5

		LinkedList<Board> rootChild2List = new LinkedList<Board>();
		rootChild2List.add(rootBoard);
		rootChild2List.add(rootBoard);
		rootChildNode2.setList(rootChild2List);

		Node level2Child3 = rootChildNode2.getList().get(0);
		level2Child3.setEvaluation(2);// s6
		Node level2Child4 = rootChildNode2.getList().get(1);
		level2Child4.setEvaluation(1);// s7

		t.print();

		return t;

	}

	public static void main(String[] args) {

		// try {
		String[] fen = new String[2];
		fen[0] = "r1b4k/3q2rp/8/2pP1Q2/1pB3n1/7R/PP4PP/3R2K1 w - - 0 1";
		// fen[0] = "7k/8/8/5Q2/8/8/8/6K1 w - - 0 1";
		fen[1] = "";
		Board b = new Board(fen);
		System.out.println(b.toString());
		long ta = System.currentTimeMillis();
		AlphaBeta searchEngine = new AlphaBeta();
		// String bestMov = searchEngine.calculateNextMove(b, true, false, 0);
		try {
			searchEngine.calculateNextMove(b, true, 60);
		} catch (BoardException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		searchEngine.stopSearching();
		long tb = System.currentTimeMillis();
		long time = (tb - ta) / 1000;
		System.out.println("Zeit: " + time);
		System.out
				.println("##############################################################################");
		System.out.println("Bester Zug: " + searchEngine.bestMove);
		// } catch (BoardException e) {
		// TODO Auto-generated catch block
		// e.printStackTrace();
		// }

		// GameTree tree = AlphaBeta.buildTestTree();
		//
		// try {
		// searchEngine.alphaBeta(tree.getRoot(),
		// Integer.MIN_VALUE,Integer.MAX_VALUE, true);
		// int max =-1000000000;
		// int index=-1;
		// for(Node n : tree.getRoot().getList()){
		// int tmp = n.getEvaluation();
		// if(tmp>max){
		// max = tmp;
		// index = tree.getRoot().getList().indexOf(n);
		// }else{
		// continue;
		// }
		// }
		// System.out.println("Knoten mit bester Bewertung auf : "+index+
		// ", Bewertung :"+max);
		// } catch (BoardException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }

	}

}
